<?php

namespace App\Handlers\Commands;

use App\Commands\CreateVulnerabilityReference as CreateVulnerabilityReferenceCommand;
use App\Entities\VulnerabilityReferenceCode;
use App\Entities\Vulnerability;
use App\Exceptions\ActionNotPermittedException;
use App\Exceptions\InvalidInputException;
use App\Exceptions\UserNotFoundException;
use App\Exceptions\VulnerabilityNotFoundException;
use App\Exceptions\WorkspaceNotFoundException;
use App\Policies\ComponentPolicy;
use App\Repositories\VulnerabilityReferenceCodeRepository;
use App\Repositories\VulnerabilityRepository;
use Doctrine\ORM\EntityManager;
use Exception;

class CreateVulnerabilityReference extends CommandHandler
{
    /** @var VulnerabilityRepository */
    protected $vulnerabilityRepository;

    /** @var VulnerabilityReferenceCodeRepository */
    protected $vulnerabilityRefRepository;

    /** @var EntityManager */
    protected $em;

    /**
     * CreateAsset constructor.
     *
     * @param VulnerabilityRepository $assetRepository
     * @param VulnerabilityReferenceCodeRepository $vulnerabilityReferenceCodeRepository
     * @param EntityManager $em
     */
    public function __construct(
        VulnerabilityRepository $assetRepository,
        VulnerabilityReferenceCodeRepository $vulnerabilityReferenceCodeRepository, EntityManager $em
    )
    {
        $this->vulnerabilityRepository    = $assetRepository;
        $this->vulnerabilityRefRepository = $vulnerabilityReferenceCodeRepository;
        $this->em                         = $em;
    }

    /**
     * Process the CreateAsset command.
     *
     * @param CreateVulnerabilityReferenceCommand $command
     * @return VulnerabilityReferenceCode
     * @throws InvalidInputException
     * @throws UserNotFoundException
     * @throws WorkspaceNotFoundException
     * @throws Exception
     */
    public function handle(CreateVulnerabilityReferenceCommand $command)
    {
        // Get the authenticated User
        $requestingUser = $this->authenticate();
        
        // Check that all the required fields were set on the command
        $vulnerabilityId = $command->getId();
        /** @var VulnerabilityReferenceCode $entity */
        $entity          = $command->getEntity();
        
        if (!isset($vulnerabilityId, $entity)) {
            throw new InvalidInputException("One or more required members are not set on the command");
        }

        // Make sure the given Vulnerability exists
        /** @var Vulnerability $vulnerability */
        $vulnerability = $this->vulnerabilityRefRepository->find($vulnerabilityId);
        if (empty($vulnerability)) {
            throw new VulnerabilityNotFoundException("No Asset with the given ID was found in the database");
        }

        // Make sure the authenticated User has permission to add/edit information related to the given Vulnerability
        if ($requestingUser->cannot(ComponentPolicy::ACTION_UPDATE, $vulnerability)) {
            throw new ActionNotPermittedException(
                "The authenticated User does not have permission to"
                . " add a Vulnerability Reference to the given Vulnerability"
            );
        }

        // Check if this Vulnerability reference already exists for this Vulnerability and if so, exit early
        $entity->setVulnerability($vulnerability)
            ->setVulnerabilityId($vulnerabilityId);

        $vulnerabilityReference = $this->vulnerabilityRepository->findOneBy($entity->getUniqueKeyColumns()->all());
        if (!empty($vulnerabilityReference) && $vulnerabilityReference instanceof VulnerabilityReferenceCode) {
            $entity = $vulnerabilityReference->setFromArray($entity->toArray(true));
        }

        // Persist the new Asset to the database
        $this->em->persist($entity);

        // Save immediately if we're not in multi-mode
        if (!$command->isMultiMode()) {
            $this->em->flush($entity);
        }

        return $entity;
    }

    /**
     * @return VulnerabilityRepository
     */
    public function getVulnerabilityRepository()
    {
        return $this->vulnerabilityRepository;
    }

    /**
     * @return VulnerabilityReferenceCodeRepository
     */
    public function getVulnerabilityRefRepository(): VulnerabilityReferenceCodeRepository
    {
        return $this->vulnerabilityRefRepository;
    }

    /**
     * @return EntityManager
     */
    public function getEm()
    {
        return $this->em;
    }
}